﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace Nirvana
{
    public abstract class Spline : MonoBehaviour
    {

        public abstract event Action ChangedEvent;

        public abstract Vector3 GetPosition(float t);

        public abstract Vector3 GetVelocity(float t);

        public abstract Vector3 GetDirection(float t);
    }

    /// <summary>
    /// 贝兹曲线
    /// </summary>
    public sealed class BezierSpline : Spline
    {
        public enum PointMode
        {
            Free,
            //对齐 
            Aligned,
            //相反
            Mirrored
        }

        [SerializeField]
        private Vector3[] points;
        [SerializeField]
        private PointMode[] modes;
        [SerializeField]
        private bool loop;
        private Action OnChangedEvent;

        public override event Action ChangedEvent
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            add
            {
                this.OnChangedEvent += value;
            }
            [MethodImpl(MethodImplOptions.Synchronized)]
            remove
            {
                this.OnChangedEvent -= value;
            }
        }

        public bool Loop
        {
            get
            {
                return this.loop;
            }
            set
            {
                this.loop = value;
                if (value)
                {
                    this.modes[this.modes.Length - 1] = this.modes[0];
                    this.SetPoint(0, this.points[0]);
                }
            }
        }
        public int PointCount
        {
            get
            {
                return this.points.Length;
            }
        }

        public int CurveCount
        {
            get
            {
                return (this.points.Length - 1) / 3;
            }
        }
        public Vector3 GetPoint(int index)
        {
            return this.points[index];
        }

        public void SetPoint(int index, Vector3 point)
        {
            if (index % 3 == 0)
            {
                Vector3 vector = point - this.points[index];
                if (this.loop)
                {
                    if (index == 0)
                    {
                        this.points[1] += vector;
                        this.points[this.points.Length - 2] += vector;
                        this.points[this.points.Length - 1] = point;
                    }
                    else if (index == this.points.Length - 1)
                    {
                        this.points[0] = point;
                        this.points[1] += vector;
                        this.points[index - 1] += vector;
                    }
                    else
                    {
                        this.points[index - 1] += vector;
                        this.points[index + 1] += vector;
                    }
                }
                else
                {
                    if (index > 0)
                    {
                        this.points[index - 1] += vector;
                    }
                    if (index + 1 < this.points.Length)
                    {
                        this.points[index + 1] += vector;
                    }
                }
            }
            this.points[index] = point;
            this.SetCurve(index);
            if (this.OnChangedEvent != null)
            {
                this.OnChangedEvent();
            }
        }
        public PointMode GetPointMode(int index)
        {
            return this.modes[(index + 1) / 3];
        }

        public void SetPointMode(int index, PointMode mode)
        {
            int num = (index + 1) / 3;
            this.modes[num] = mode;
            if (this.loop)
            {
                if (num == 0)
                {
                    this.modes[this.modes.Length - 1] = mode;
                }
                else if (num == this.modes.Length - 1)
                {
                    this.modes[0] = mode;
                }
            }
            this.SetCurve(index);
            if (this.OnChangedEvent != null)
            {
                this.OnChangedEvent();
            }
        }

        public void AddCurve()
        {
            Vector3 vector = this.points[this.points.Length - 1];
            Vector3 normalized = (this.points[this.points.Length - 1] - this.points[this.points.Length - 2]).normalized;
            Array.Resize<Vector3>(ref this.points, this.points.Length + 3);
            vector += normalized;
            this.points[this.points.Length - 3] = vector;
            vector += normalized * 2f;
            this.points[this.points.Length - 2] = vector;
            vector += normalized * 3f;
            this.points[this.points.Length - 1] = vector;
            Array.Resize<PointMode>(ref this.modes, this.modes.Length + 1);
            this.modes[this.modes.Length - 1] = this.modes[this.modes.Length - 2];
            this.SetCurve(this.points.Length - 4);
            if (this.loop)
            {
                this.points[this.points.Length - 1] = this.points[0];
                this.modes[this.modes.Length - 1] = this.modes[0];
                this.SetCurve(0);
            }
            if (this.OnChangedEvent != null)
            {
                this.OnChangedEvent();
            }
        }

        public override Vector3 GetPosition(float t)
        {
            int num;
            if (t >= 1f)
            {
                t = 1f;
                num = this.points.Length - 4;
            }
            else
            {
                t = Mathf.Clamp01(t) * (float)this.CurveCount;
                num = (int)t;
                t -= (float)num;
                num *= 3;
            }
            Vector3 vector = Vector3Tool.Lerp(this.points[num], this.points[num + 1], this.points[num + 2], this.points[num + 3], t);
            return base.transform.TransformPoint(vector);
        }

        public override Vector3 GetVelocity(float t)
        {
            int num;
            if (t >= 1f)
            {
                t = 1f;
                num = this.points.Length - 4;
            }
            else
            {
                t = Mathf.Clamp01(t) * (float)this.CurveCount;
                num = (int)t;
                t -= (float)num;
                num *= 3;
            }
            Vector3 vector = Vector3Tool.Dot(this.points[num], this.points[num + 1], this.points[num + 2], this.points[num + 3], t);
            return base.transform.TransformPoint(vector) - base.transform.position;
        }

        public override Vector3 GetDirection(float t)
        {
            return this.GetVelocity(t).normalized;
        }

        private void SetCurve(int A_1)
        {
            int num = (A_1 + 1) / 3;
            PointMode pointMode = this.modes[num];
            if (pointMode == PointMode.Free)
            {
                return;
            }
            if (!this.loop)
            {
                return;
            }
            if (num == 0 || num == this.modes.Length - 1)
            {
                return;
            }
            int num2 = num * 3;
            int num3;
            int num4;
            if (A_1 <= num2)
            {
                num3 = num2 - 1;
                if (num3 < 0)
                {
                    num3 = this.points.Length - 2;
                }
                num4 = num2 + 1;
                if (num4 >= this.points.Length)
                {
                    num4 = 1;
                }
            }
            else
            {
                num3 = num2 + 1;
                if (num3 >= this.points.Length)
                {
                    num3 = 1;
                }
                num4 = num2 - 1;
                if (num4 < 0)
                {
                    num4 = this.points.Length - 2;
                }
            }
            Vector3 vector = this.points[num2];
            Vector3 vector2 = vector - this.points[num3];
            if (pointMode == PointMode.Aligned)
            {
                vector2 = vector2.normalized * Vector3.Distance(vector, this.points[num4]);
            }
            this.points[num4] = vector + vector2;
        }

        private void Reset()
        {
            this.points = new Vector3[]
            {
                new Vector3(1f, 0f, 0f),
                new Vector3(2f, 0f, 0f),
                new Vector3(3f, 0f, 0f),
                new Vector3(4f, 0f, 0f)
            };
            this.modes = new PointMode[2];
            if (this.OnChangedEvent != null)
            {
                this.OnChangedEvent();
            }
        }

    }
}


